﻿/*
    AAATrip rappresenta un oggetto viaggio, ed in pratica è una estensione di un
    GeoTrip lato server. I parametri del costruttore sono:
    - trip = risultato dell'eval della serializzazione in json di un GeoTrip
    - isEditable = indica se il viaggio è editabile o meno
    - aaaRoutes = parametro opzionale che specifica gli oggetti AAARoutes che costituiscono
        il viaggio.
*/

MapControl2.AAATrip = function(trip, isEditable, map, aaaRoutes)
{
    this._tripModel = trip;             
    this._isEditable = isEditable;
    this._markers = new Array();
    this._routesReady = false;
    this._parentMap = map;
    this._status = AAASTATUS_LOADING;
    var vf = new MapControl2.AAAVisitorFactory()
    this._myFormatter = vf.createVisitor
        (MapControl2.AAAVisitorFactory.MARKERS_FORMATTER_VISITOR);
    
    if (aaaRoutes)
    {
        this._routesReady = true;
        this._routes = aaaRoutes;
    }else{
        this._routes = new Array();
    }
    this._bounds = null;
    
    this._createRoutesAndMarkers();
    this._refresh_consistency();
    this._refreshBounds();
    this._status = AAASTATUS_OK;
    this._forcedMarkerDragEnd = false;
};

MapControl2.AAATrip.prototype = {
    
    // --------------- private functions ---------------------------------------------
    _refresh_consistency: function()
    {
        for (var i = 0; i<this._routes.length; i++)
        {
            this._routes[i]._routeModel = this._tripModel.routes[i];
        }
        for (var i = 0; i<this._routes.length - 1; i++)
        {
            this._routes[i]._routeModel.end = this._routes[i + 1]._routeModel.start;
        }
        for (var i = 0; i<this._markers.length - 1; i++)
        {
            this._markers[i]._geoloc = this._routes[i]._routeModel.start;
        }
        this._markers[this._markers.length - 1]._geoloc = this._routes[this._routes.length - 1]._routeModel.end;
    },
    
    _refreshBounds: function(){
        this._bounds = new GLatLngBounds(), currPolyBounds = null;
        for (var i=0; i<this._routes.length; i++){
            currPolyBounds = this._routes[i].getPolyline().getBounds();
            this._bounds.extend(currPolyBounds.getNorthEast());
            this._bounds.extend(currPolyBounds.getSouthWest());
        }
    },
    
    _createRoutesAndMarkers: function()
    {
        var trip = this._tripModel;
        var current_route = null;
        var marker_type = -1;
        var new_marker = null;
        var i = 0;
        var aaaRoute = null;
        
        for (i = 0; i<trip.routes.length; i++)
        {
            current_route = trip.routes[i];
            if (!this._routesReady)
            {
                aaaRoute = new MapControl2.AAARoute
                    (current_route, trip.GPolyline[i], this._isEditable, false, this._parentMap);
                this._routes.push(aaaRoute);
            }else
            {
                aaaRoute = this._routes[i];
            }
            aaaRoute.setParentTrip(this);
            
            if (i == 0)
            {
                marker_type = MapControl2.AAAMarker.START_MARKER;
            }else
            {
                marker_type = MapControl2.AAAMarker.STOP_MARKER;
            }
            new_marker = MapControl2.AAAUtils.createMarkerFromPoint
                    (current_route.start, marker_type, !this._isEditable);
            if (this._routesReady){            
                new_marker.setLatLng(aaaRoute.getPolyline().getVertex(0));
                new_marker._geoloc.lat = new_marker.getLatLng().lat();
                new_marker._geoloc.lng = new_marker.getLatLng().lng();
                new_marker._geoloc.gettingInUsers = new Array();
                new_marker._geoloc.gettingOffUsers = new Array();
            }
            if (this._isEditable)
            {
                google.maps.Event.bind(new_marker, 'dragend2', this, this._routeMarkerDragEnd);
                GEvent.bind(new_marker, 'rightclick', this, this._routeMarkerRightClick);
            }
            this._markers.push(new_marker);
            this._parentMap.addOverlay(new_marker);
        }
        
        var end_marker = MapControl2.AAAUtils.createMarkerFromPoint
            (current_route.end, MapControl2.AAAMarker.END_MARKER, !this._isEditable)   
        if (this._isEditable)
        {
            google.maps.Event.bind(end_marker, 'dragend2', this, this._routeMarkerDragEnd);
            GEvent.bind(end_marker, 'rightclick', this, this._routeMarkerRightClick);
        }
        if (this._routesReady){
            end_marker.setLatLng(aaaRoute.getPolyline().getVertex(aaaRoute.getPolyline().getVertexCount()-1));
            end_marker._geoloc.lat = end_marker.getLatLng().lat();
            end_marker._geoloc.lng = end_marker.getLatLng().lng();
            end_marker._geoloc.gettingInUsers = new Array();
            end_marker._geoloc.gettingOffUsers = new Array();
            
        }
        this._markers.push(end_marker);               
        this._parentMap.addOverlay(end_marker);
    },
    
    _updateTripInfo: function()
    {
        var i = 0;
        var new_covered_distance = 0;
        var new_spent_time = 0;
        for (i = 0; i<this._tripModel.routes.length; i++)
        {
            new_covered_distance += this._tripModel.routes[i].distance;
            new_spent_time += this._tripModel.routes[i].time;
        }
        
        var d = new Number(new_covered_distance);
        d = d.toFixed(3);
        
        this._tripModel.CoveredDistance = parseFloat(d);
        this._tripModel.SpentTime = new_spent_time;
        google.maps.Event.trigger(this._parentMap, 'tripchange', this);
    },
    
    addDestination: function(latlng){
        if (this._status != AAASTATUS_OK) return;
        this._status = AAASTATUS_LOADING;
        var mIndex = this._markers.length - 1, lastM = this._markers[mIndex], gDir = new GDirections(), me=this;
        var net = new MapControl2.AAANetUtils(), gLoc = MapControl2.AAAUtils.createGeoLocFromLatLng(latlng);
        var gLocs = new Array(lastM.getGeoLoc(), gLoc), lastRoute = this._routes[mIndex - 1]._routeModel;
        var newM = new MapControl2.AAAMarker(gLoc, false, MapControl2.AAAMarker.END_MARKER, '...');
        
        lastM.setType(MapControl2.AAAMarker.STOP_MARKER); 
        net.performGoogleRequest(gDir, [lastM.getLatLng(), latlng], function(){gDirCb(true);}, 
                                                                    function(e){gDirCb(false, e);}); 
        
        var gDirCb = function(ok, err){
            if (ok)
                newM.setLatLng(gDir.getPolyline().getVertex(gDir.getPolyline().getVertexCount() - 1));
            var newRoute = MapControl2.AAARoute.createRouteFromGDirectionsByGLocs
              (gLocs, gDir, me._parentMap, {'Class':lastRoute.vehicle, 'Id':lastRoute.vehicleId});
            newRoute.setParentTrip(me);
            GEvent.bind(newRoute, 'route_changed', me, me._routeChanged);
            GEvent.bind(newRoute, 'route_error', me, me._routeError);
            GEvent.bind(newRoute, 'route_click', me, me._routeClick);
            me._routes.push(newRoute);
            me._tripModel.routes.push(newRoute._routeModel);
            me._markers.push(newM);
            lastRoute.end = newRoute._routeModel.start;
            me._parentMap.addOverlay(newM);
            GEvent.bind(newM, 'dragend2', me, me._routeMarkerDragEnd);
            GEvent.bind(newM, 'rightclick', me, me._routeMarkerRightClick);
            newM.checkPosition(checkCb);
        }, checkCb = function(){
            newM._mustBeFormatted = true;
            lastM._mustBeFormatted = true;
            me._markers[0]._mustBeFormatted = true;
            me._refreshBounds();
            me._updateTripInfo(); 
            me._status = AAASTATUS_OK; 
        };  
    },
    
    insertStageFromMarker: function(routeIndex, waypointIndex, aaaMarker, ok_callback)
    {
        this._status = AAASTATUS_LOADING;
        var AMarker = this._markers[routeIndex];
        var BMarker = this._markers[routeIndex+1];
        var route = this.getRoutes()[routeIndex];
       
        // inserisco nella lista di marker del trip questo marker
        this._markers.splice(routeIndex+1,0,aaaMarker);
       
        // ristringo la lista dei waypoint della tratta da spezzare
        var wpIndex = route._waypointMarkers.length;
        if(wpIndex > waypointIndex)
            wpIndex = waypointIndex;
            
        var modelWPB = route._routeModel.viaPoints.splice(wpIndex, route._routeModel.viaPoints.length);
        var markersWPB = route._waypointMarkers.splice(wpIndex, route._waypointMarkers.length);
        route._routeModel.end = aaaMarker._geoloc;
        
        var llB = new Array(); // latlng list to create new route from aaaMarker to BMarker
        llB.push(aaaMarker.getLatLng());
        
        // rimuovo i vecchi marker dei viapoint perchè la creazione della nuova tratta li ricrea
        for(var i=0; i<markersWPB.length; i++)
        {
            llB.push(markersWPB[i].getLatLng());
            google.maps.Event.clearInstanceListeners(markersWPB[i]);
            this._parentMap.removeOverlay(markersWPB[i]);
        }
        llB.push(BMarker.getLatLng());
        
        modelWPB.splice(0,0, aaaMarker._geoloc);    // setto la primo geoloc della nuova route
        modelWPB.push(BMarker._geoloc);             // setto l'ultimo geoloc della nuova route
        
        // Si ricalcolano i percorsi, però gli eventi route_change si sollevano solo quando abbiamo enrambe
        // le tratte per poter invocare il marker formatter sul nuovo trip con una tratta in più
        var newRoute = null;
        var oldRouteRefreshed = false;
        route.refreshPolyline(refreshedOldRoute);
        
        var gDir = new google.maps.Directions();
        var tmpNet = new MapControl2.AAANetUtils();
        tmpNet.performGoogleRequest(gDir, llB, 
            Function.createDelegate(this, gDirectionsLoadedB));
        
        var vehicle = new Object();
        vehicle.Id = route._routeModel.vehicleId;
        vehicle.Class = route._routeModel.vehicle;
        
        function refreshedOldRoute()
        {
            refreshedOldRoute = true;
            if(newRoute != null)
                end();
        }
        
        // per la chiusura lessicale
        var thisTrip = this;
        
        function gDirectionsLoadedB()
        {
            aaaMarker._mustBeFormatted = true;
            thisTrip._markers[0]._mustBeFormatted = true;
            newRoute = MapControl2.AAARoute.createRouteFromGDirectionsByGLocs
                (modelWPB, gDir, thisTrip._parentMap, vehicle);
            newRoute.setParentTrip(thisTrip);
            this._routes.splice(routeIndex+1, 0, newRoute);
            this._tripModel.routes.splice(routeIndex+1, 0, newRoute._routeModel);
            
            if(refreshedOldRoute)
                end();      
        };
        
        function end()
        {
            GEvent.bind(aaaMarker, 'rightclick', thisTrip, thisTrip._routeMarkerRightClick);
            google.maps.Event.trigger(route, 'route_changed', route);
            google.maps.Event.trigger(newRoute, 'route_changed', newRoute);
            if(ok_callback != null)
                ok_callback();
            else
                thisTrip._status = AAASTATUS_OK;
        };
    },
    
    removeStageFromMarker: function(aaaMarker, forceRefresh, ok_callback)
    {
        this._status = AAASTATUS_LOADING;
        var index = this._findStageMarkerIndex(aaaMarker);
        
        // it's impossible deleting trip's start/end or a marker inesistent in trip
        if(index == -1 || index == 0 || index == this._markers.length-1) 
            return false;
        
        var routeA = null;
        var routeB = null;
        if(index-1 >= 0)
            routeA = this._routes[index-1];
        if(index<this._routes.length)
            routeB = this._routes[index];
        
        // si rimuove il marker dalla lista
        this._markers.splice(index,1);
        
        // si aggiorna la lista di routes
        if(routeA == null)
        {
            // si elimina routeB
            routeB.clear();
            this._routes.splice(index, 1);
            this._tripModel.routes.splice(index, 1);
        }
        else if(routeB == null)
        {
            // si elimina routeA
            routeA.clear();
            this._routes.splice(index-1, 1);
            this._tripModel.routes.splice(index-1, 1);
        }
        else
        {
            // si elimina routeB e si appendono waypoint di routeB ad routeA cambiando anche la destinazione di A in quella di B
            routeA._routeModel.end = routeB._routeModel.end;
            routeA._routeModel.viaPoints = routeA._routeModel.viaPoints.concat(routeB._routeModel.viaPoints);
            
            for(var i=0; i<routeB._waypointMarkers.length; i++)
            {
                // si aggiungono ai waipoint di routeA dei waypoint localizzati dove erano quelli di routeB
                var newWMarker = MapControl2.AAAMarker.createMarker
                    (routeB._waypointMarkers[i]._geoloc, !this._isEditable, MapControl2.AAAMarker.WAYPOINT_MARKER, '');
                
                this._parentMap.addOverlay(newWMarker);    
                routeA._waypointMarkers.push(newWMarker);
                google.maps.Event.bind(newWMarker, 'dragend2', routeA, routeA._waypointDragEnd);
                
                google.maps.Event.clearInstanceListeners(routeB._waypointMarkers[i]);
                this._parentMap.removeOverlay(routeB._waypointMarkers[i]);
            }
            
            this._routes.splice(index, 1);
            this._tripModel.routes.splice(index, 1);
            
            if(forceRefresh == null || forceRefresh)
                routeA.refreshPolyline(function f(){google.maps.Event.trigger(routeA, 'route_changed', routeA);});
            
            // Elimino la componente grafica della "seconda" polilinea
            if(routeB._polyline != null)
            {
                this._parentMap.removeOverlay(routeB._polyline);
                google.maps.Event.clearInstanceListeners(routeB._polyline);
            }
            google.maps.Event.clearInstanceListeners(routeB);
         }
        
        if(ok_callback != null)
            ok_callback();
        else
            this._status = AAASTATUS_OK;
        return true;
    },
    
    insertStageFromGeoloc: function(geoloc, routeIndex, waypointIndex, ok_callback)
    {
        this._status = AAASTATUS_LOADING;
        var new_marker = MapControl2.AAAUtils.createMarkerFromPoint
                    (geoloc, MapControl2.AAAMarker.STOP_MARKER, !this._isEditable);
        this._parentMap.addOverlay(new_marker);
        google.maps.Event.bind(new_marker, 'dragend2', this, this._routeMarkerDragEnd);
        GEvent.bind(new_marker, 'rightclick', this, this._routeMarkerRightClick);
        this.insertStageFromMarker(routeIndex, waypointIndex, new_marker, ok_callback);
    },
    
    _findStageMarkerIndex: function(marker)
    {
        for(var i=0; i<this._markers.length; i++)
            if(this._markers[i] == marker)
            return i;
        return -1;
    },
    
    _findRouteIndex: function(route){
        for (var i=0; i<this._routes.length; i++)
            if (this._routes[i] == route)
                return i;
        return -1;
    },
    
    _findWaypointMarkerIndex: function(marker)
    {
        var res = new Object();
        res.routeIndex = -1;
        res.waypointIndex = -1;
        
        for(var i=0; i<this._routes.length; i++)
        {
            var waypoints = this._routes[i]._waypointMarkers;
            for(var j=0; j<waypoints.length; j++)
            {
                if(waypoints[j] == marker)
                {   
                    res.routeIndex = i;
                    res.waypointIndex = j;
                    break;
                }
            }
        }
        return res;
    },
    // -------------------------------------------------------------------------------
    
    // ----------- event handlers ----------------------------------------------------
    _routeMarkerRightClick: function(point, src, marker){
        var menu = this._parentMap._menu;
        var markerIndex = this._findStageMarkerIndex(marker);
        menu._markerClicked = marker;
        menu._markerClickedIndex = markerIndex;
        menu.showMenu(point, 2);
    },
    
    _routeMarkerDragEnd: function(marker){
        marker._dontshow = true;
        var prevRoute = null, nextRoute = null, prevMarker = null, maxMarker = this._markers.length - 1;
        var index = -1, me = this;
        for (var i=0; i<this._markers.length; i++)
            if (this._markers[i] == marker){index = i; break;}
        
        (index == 0)? nextRoute = this._routes[0] : prevRoute = this._routes[index - 1];
        (index == maxMarker)? nextRoute = null : nextRoute = this._routes[index];
        if (prevRoute != null) prevMarker = this._markers[index - 1];
        
        var errCb = function(e, r){
            marker.setError();
            marker._dontshow = false;
            GEvent.trigger(me._parentMap, 'triperror', me, r, {type:'google_error', err: e});
        },
        refreshCb = function(routeRefreshed, otherRoute){
            if (otherRoute == null || otherRoute.getStatus()!= AAASTATUS_LOADING){
                var poly = routeRefreshed.getPolyline(), polyVertex = null;
                if (routeRefreshed == prevRoute)
                    polyVertex = poly.getVertex(poly.getVertexCount() - 1);
                else
                    polyVertex = poly.getVertex(0);
                marker.setLatLng(polyVertex);  
                if (me._forcedMarkerDragEnd){
                    me._forcedMarkerDragEnd = false;
                    revCb();
                }else
                    marker.checkPosition(revCb);
            }
        },
        revCb = function(){
            if (prevMarker != null) prevMarker._mustBeFormatted = true;
            me._refreshBounds();
            marker._dontshow = false;
            me._updateTripInfo();
        };
        
        if (prevRoute != null)
            prevRoute.refreshPolyline(function(){refreshCb(prevRoute, nextRoute);}, function(e){errCb(e, prevRoute);});
        if (nextRoute != null)
            nextRoute.refreshPolyline(function(){refreshCb(nextRoute, prevRoute);}, function(e){errCb(e, nextRoute);});
    },
    
    _routeChanged: function(aaaRoute)
    {
        var index = 0;
        for (var i = 0; i<this._routes.length; i++)
        {
            if (aaaRoute == this._routes[i])
            {
                index = i;
                break;
            }
        }
        
        var poly = aaaRoute.getPolyline(), startMarker = this._markers[index];
        var me = this, refreshCb = function(){
            me._markers[0]._mustBeFormatted = true;
            if (index != 0)
            {
                me._markers[index]._mustBeFormatted = true;
            }
            me._refreshBounds();
            me._updateTripInfo();
        };
        
        if (startMarker.getLatLng().distanceFrom(poly.getVertex(0))>2){
            
            startMarker.setLatLng(poly.getVertex(0));
            this._markers[index].updateStatus();
            if (index > 0){
                var prevRoute = this._routes[index-1];
                prevRoute.refreshPolyline(refreshCb);
            }
        }else{
            refreshCb();
        }
    },
    
    _routeError: function(aaaRoute, error)
    {
        this._refreshBounds();
        google.maps.Event.trigger(this._parentMap, 'triperror', this, aaaRoute, {type:'google_error', err: error});
    },
    
    _routeClick: function(aaaRoute, subrouteIndex, rightClick, polyLatLng, mousePos){
        if(rightClick == true)
        {  
            var menu = this._parentMap._menu;
            var routeIndex = this._findRouteIndex(aaaRoute);
            menu._routeSelected = aaaRoute;
            menu._routeSelectedIndex = routeIndex;
            menu._subrouteIndex = subrouteIndex;
            menu.showMenu(mousePos, 1);
        }
    },
    // -------------------------------------------------------------------------------
    
    // ------------- properties ------------------------------------------------------
    isEditable: function()
    {
        return (this._isEditable);
    },
    getTripModel: function()
    {
        return (this._tripModel);
    },
    getMarkers: function()
    {
        return (this._markers);
    },
    getRoutes: function()
    {
        return (this._routes);
    },
    getParentMap: function()
    {
        return (this._parentMap);
    },
    setParentMap: function(map)
    {
        this._parentMap = map;
    },
    
    getBounds: function(){
        return this._bounds;
    },
    // -------------------------------------------------------------------------------
    
    // --------------- public functions ----------------------------------------------
    // rende questo oggetto "visitabile"
    accept: function(visitor)
    {
        visitor.visit(this, MapControl2.AAATRIP_ELEMENT);
    },
    
    getStatus: function()
    {
        for(var r=0; r<this._routes.length; r++)
            if(this._routes[r].getStatus() != AAASTATUS_OK)
                return this._routes[r].getStatus();
            
        for(var m=0; m<this._markers.length; m++)
            if( this._markers[m]._geoloc.address == '...')
                return AAASTATUS_LOADING;
    },
    
    clear: function()
    {
        while(this._markers.length>0)
        {
            var marker = this._markers.pop();
            google.maps.Event.clearInstanceListeners(marker);
            this._parentMap.removeOverlay(marker);
        }
    
        while(this._routes.length>0)
        {
            var route = this._routes.pop();
            route.clear()
        }
        
        var routesModel = this.getTripModel().routes;
        routesModel.slice(0,routesModel.length);
        
        google.maps.Event.trigger(this._parentMap, 'tripclear', this);
    },
    
    
    setDeviationRadius: function(kms){
        this._tripModel.deviationRadius = kms;
        for (var i = 0; i<this._routes.length; i++){
            this._routes[i].setDeviationRadius(kms);
        }
    },
    
    isInDeviation: function(latlng){
        var res = {'result': false, 'deviation': 1000000}, tmpDev;
        for (var i=0; i<this._routes.length; i++){
            tmpDev = this._routes[i].isInDeviation(latlng);
            if (tmpDev.deviation < res.deviation)
                res.deviation = tmpDev.deviation;
            if (tmpDev.result)
                res.result = true;
        }
        return res;
    },
    
    setVehicle: function(v){
        for (var i=0; i<this._routes.length; i++){
            this._routes[i].setVehicle(v);
        }
    },
    
    _setRouteMarkerPosition: function(marker, route, geoloc){
        this._forcedMarkerDragEnd = true;
        marker.hide();
        route.getPolyline().hide();
        marker.setLatLngFromGeoloc(geoloc);
        var listener = GEvent.addListener(this._parentMap, 'tripchange', 
            function(trip){
                GEvent.removeListener(listener);
                marker.show();
                trip._parentMap._centerOnTrip(trip);
            });
        GEvent.trigger(marker, 'dragend2', marker); 
    },
    
    setRouteStart: function(routeIndex, geoloc){
        this._setRouteMarkerPosition(this._markers[routeIndex], this._routes[routeIndex], geoloc);
    },
    
    setStart: function(geoloc){
        this.setRouteStart(0, geoloc);
    },
    
    setRouteEnd: function(routeIndex, geoloc){
        this._setRouteMarkerPosition(this._markers[routeIndex + 1], this._routes[routeIndex], geoloc);
    },
    
    setEnd: function(geoloc){
        this.setRouteEnd(this._routes.length - 1, geoloc);
    }
    // -------------------------------------------------------------------------------
};

// routine di creazione di un AAATrip dati n oggetti AAARoutes che ne definiscono le tratte
MapControl2.AAATrip.createTripFromAAARoutes = function(routes, map)
{
    var trip = new Object();
    var routeModels = new Array();
    var current_route = null;
    var covered_distance = 0;
    var spent_time = 0;
    
    for(i = 0; i<routes.length; i++)
    {
        current_route = routes[i].getRouteModel();
        routeModels.push(current_route);    
        covered_distance += current_route.distance;
        spent_time += current_route.time;
        if (i > 0 && i < routes.length - 1)
        {
            current_route.start = routes[i - 1].getRouteModel().end;
        }
    }
    trip.routes = routeModels;
    trip.GPolyline = new Array();
    trip.id = -1;
    trip.CoveredDistance = covered_distance;
    trip.SpentTime = parseInt(spent_time);
    trip.deviationRadius = 0;
    
    var aaaTrip = new MapControl2.AAATrip(trip, true, map, routes);
    return aaaTrip;
};
// -----------------------------------------------------------------------------------------

MapControl2.AAATrip.registerClass('MapControl2.AAATrip');

if (typeof(Sys) !== 'undefined') Sys.Application.notifyScriptLoaded();
